package stage;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.Coordinate;
import java.util.Vector;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;


/**
 * Classe che implementa i metodi di supporto per l'inserimento di una geometria
 * all'interno della SpatialGrid
 *
 * @author Matteo Cuccato
 * @author Nicola Girardi
 */
public class GridUtils {

    /**
     * Metodo che data un GeometryContainer controlla se nella SpatialGrid
     * interseca qualche oggetto, le intersezioni trovate vengono salvate
     * in un vector. Mentre nella SpatialGrid vengono aggiornati i valori
     * de n_risultati delle geometry che hanno prodotto risultati.
     *
     * @param sg SpatialGrid in cui controllare le intersezioni
     * @param gc GeometryContainer di cui controllare le intersezioni
     * @return Vector con le intersezioni
     * @exception NullPointerException Se uno dei parametri è null
     */
    public static Vector join(SpatialGrid sg, GeometryContainer gc) throws NullPointerException{
        if ( sg == null )
            throw new NullPointerException("SpatialGrid is null");
        if ( gc == null )
            throw new NullPointerException("GeometryContainer is null");

        Vector v = new Vector();
        if( !sg.isEmpty() ){
            Geometry q = quad(sg), g = gc.getGeometry(), geom, intersezione;
            GeometryIntersection gi;
            GeometryContainer app;
            
            if ( g.intersects(q) ){

                int[] c = trovaCelle(sg, g);
                int x_i = c[0], x_f = c[1], y_i = c[2], y_f = c[3];

//                System.out.println("(JOIN) celle da controllare:\nX_i: "+x_i+", x_f: "+x_f+"\ny_i: "+y_i+", y_f: "+y_f);
                ListaLinked ln = new ListaLinked();

                for( int i = y_i; i <= y_f; i++ ){
                    for( int j = x_i; j <= x_f; j++ ){
                        ListaLinked ll = sg.getLL(i, j);

                        if( !ll.isEmpty() ){

                            for( int k = 0; k < ll.size(); k++ ){
                                app = (GeometryContainer)ll.get(k);
                                geom = app.getGeometry();

                                intersezione = g.intersection(geom);

                                if ( intersezione != null && !intersezione.isEmpty() ){
                                    gi = new GeometryIntersection(gc.getId(), app.getId(), gc.getGeometry());
                                    if ( insert(v, gi) ){
                                        app.setN_risultati( app.getN_risultati() + 1 );
                                        sg.updateResDist(app);
                                        gc.setN_risultati( gc.getN_risultati() + 1 );
                                    }
//                                    ll.remove(k);
//                                    ll.insert(k, app);
                                    ll.set(k, app);
                                }
                            }

                        }

                        sg.insertLL(i, j, ll);
                        ln = new ListaLinked();
                    }
                }
            }
        }
        return v;
    }


    /**
     * Metodo per trovare le celle in cui cade un mbr all'interno
     * di una SpatialGrid
     *
     * @param sg SpatialGrid in cui controllare
     * @param g Geometry di cui trovare le dimensioni
     * @return int array posizione 0 = x_i; 1 = x_f; 2 = y_i; 3 = y_f
     */
    private static int[] trovaCelle(SpatialGrid sg, Geometry g){
        int[] c = new int[4];

        /**
         * Calcolo dell'x_minore x_maggiore y_minore y_maggiore
         * Il calcolo di queste coodinate viene trovando la parte intera
         * della divisione tra la coordinata di interesse e la grandezza delle
         * celle della SpatialGrid.
         * Per calcolare le y_minore e y_maggiore, siccome le coordinate presenti
         * nei file .jml utilizzano un sistema di riferimento cartesiano O(x,y),
         * abbiamo dovuto mappare il fatto che la nostra griglia strutturata come
         * matrice avesse gli indici che partono dallo [0][0] in alto a sinistra
         */

        // Trovo le caselle della SpatialGrid dove cade l'mbr
//        System.out.println("(trovaCelle) g : "+g.toText());
        Geometry mbr = g.getEnvelope();
        Coordinate[] coord = mbr.getCoordinates();
        double width = sg.getWidth();

//        System.out.println("(trovaCelle)Estremi celle da controllare\ncoord[0].x : "+coord[0].x);
        int x_i = (int)(coord[0].x / width)-(int)(sg.getX_i() / width);
        int x_f = (int)(coord[2].x / width)-(int)(sg.getX_i() / width);

//        System.out.println("(trovaCelle)Estremi celle da controllare\nx_i: "+x_i+", x_f: "+x_f+"\n");
        int y_i = ((int)(sg.getRow() - 1) - ((int)(coord[0].y / width) - (int)(sg.getY_i() / width)));
        int y_f = ((int)(sg.getRow() - 1) - ((int)(coord[2].y / width) - (int)(sg.getY_i() / width)));

//        System.out.println("(trovaCelle)Estremi celle da controllare\ny_i: "+y_i+", y_f: "+y_f);

        if ( x_i < 0 )
            x_i = 0;
        if ( x_f > (sg.getColumn() - 1) )
            x_f = (int)(sg.getColumn() - 1);
        if ( y_i < 0 )
            y_i = 0;
        if ( y_f > (sg.getRow() - 1) )
            y_f = (int)(sg.getRow() - 1);
        if ( x_f < 0 )
            x_f = 0;
        if ( x_i > (sg.getColumn() - 1) )
            x_i = (int)(sg.getColumn() - 1);
        if ( y_f < 0 )
            y_f = 0;
        if ( y_i > (sg.getRow() - 1) )
            y_i = (int)(sg.getRow() - 1);

        /**
         * Sezione in cui gestiamo lo scambio delle coordinate per poi inserirle
         * giuste nei cicli for
         */
        int scambio = 0;
        if (x_i > x_f) {
            scambio = x_i;
            x_i = x_f;
            x_f = scambio;
        }//fine if
        if (y_i > y_f) {
            scambio = y_i;
            y_i = y_f;
            y_f = scambio;
        }//fine if

        if ( x_i > 0 )
            x_i --;
        if ( x_f < (sg.getColumn() - 1) )
            x_f ++;
        if ( y_i > 0 )
            y_i --;
        if ( y_f < (sg.getRow() - 1) )
            y_f ++;

        c[0] = x_i;
        c[1] = x_f;
        c[2] = y_i;
        c[3] = y_f;

        return c;
    }


    /**
     * Metodo che crea una Geometry grande come la SpatialGrid
     *
     * @param sg SpatialGrid di partenza
     * @return quad geometry che rappresenta la SpatialGrid di partenza
     */
    private static Geometry quad(SpatialGrid sg){
        double xi = sg.getX_i();
        double xf = sg.getX_f();
        double yi = sg.getY_i();
        double yf = sg.getY_f();

        Coordinate[] c = new Coordinate[]{new Coordinate(xi,yi), new Coordinate(xi,yf), new Coordinate(xf,yf), new Coordinate(xf,yi), new Coordinate(xi,yi)};
        Geometry g1 = new GeometryFactory().createLinearRing(c);
        Geometry quad = new GeometryFactory().createPolygon((LinearRing) g1, null);
        return quad;
    }

    /**
     * Metodo di appoggio per il metodo join, controlla che un'intersezione appena trovata
     * è già presente o meno nel vector che contiene tutte le intersezioni trovate
     *
     * @param v vector che contiene le intersezioni trovate
     * @param gi GeometryIntersection che deve essere inserita
     */
    private static boolean insert(Vector v, GeometryIntersection gi){
        boolean insert = true;

        if( v.size() > 0 ){
            GeometryIntersection app;
            for( int i = 0; i < v.size(); i++ ){
                app = (GeometryIntersection) v.elementAt(i);
                if( gi.getId() == app.getId() && gi.getIntersection_id() == app.getIntersection_id() )
                    insert = false;
            }
        }
        if( insert ){
             v.add(gi);
             return true;
        }
        return false;
     }


    //--------------------------------------------------------------------------
    //----------------  CREAZIONE FILE JML -------------------------------------
    //--------------------------------------------------------------------------

    /**
     * Meotodo che crea un file jml che rappresenta la struttura della griglia
     * non gli oggetti inseriti dentro
     *
     * @param SpatialGrid SpatialGrid da stampare
     * @exception NullPointerException se SpatialGrid è null
     */
    public static void createGridJml(SpatialGrid sg) throws NullPointerException{
        if ( sg == null )
            throw new NullPointerException("SpatialGrid is null");

        String localdir = System.getProperty("user.home");
        String path1 = localdir+"/Dropbox/stage/griglia.jml";

        try {
            double xi = sg.getX_i(),xf = sg.getX_f(),yi = sg.getY_i(), yf = sg.getY_f(), width = sg.getWidth() ;
            double yii=yi;
            char virgola = ',';

            File file = new File(path1);
            FileOutputStream fos = new FileOutputStream(file);
            PrintStream out = new PrintStream(fos);
                      
            out.println("<?xml version='1.0' encoding='UTF-8'?>");
            out.println("<JCSDataFile xmlns:gml=\"http://www.opengis.net/gml\" xmlns:xsi=\"http://www.w3.org/2000/10/XMLSchema-instance\">");
            out.println("<JCSGMLInputTemplate>");
            out.println("<CollectionElement>featureCollection</CollectionElement> ");
            out.println("<FeatureElement>feature</FeatureElement>");
            out.println("<GeometryElement>geometry</GeometryElement>");
            out.println("<ColumnDefinitions>");
            out.println("</ColumnDefinitions>");
            out.println("</JCSGMLInputTemplate>");
            out.println("");
            out.println("<featureCollection>");

            while (yii <= yf) {
                scriviTagIniziali(out);
                out.print("" + xi);
                out.print(virgola);
                out.print("" + yii);
                out.println("");
                out.print("" + xf);
                out.print(virgola);
                out.print("" + yii);
                scriviTagFinali(out);
                yii = yii + width;
            }
            while (xi <= xf) {
                scriviTagIniziali(out);
                out.print("" + xi);
                out.print(virgola);
                out.print("" + yi);
                out.println("");
                out.print("" + xi);
                out.print(virgola);
                out.print("" + yf);
                scriviTagFinali(out);
                xi = xi + width;
            }
            out.println("</featureCollection>");
            out.println("</JCSDataFile>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Meotodo che scrive i tag iniziali del file jml
     *
     * @param PrintStream
     */
    private static void scriviTagIniziali(PrintStream out) {
        out.println("<feature>");
        out.println("<geometry>");
        out.println("<gml:LineString>");
        out.println("<gml:coordinates>");
    }

    /**
     * Meotodo che scrive i tag finali del file jml
     *
     * @param PrintStream
     */
    private static void scriviTagFinali(PrintStream out) {
        out.println("</gml:coordinates>");
        out.println("</gml:LineString>");
        out.println("</geometry>");
        out.println("</feature>\n");
    }

    /**
     * Crea o modifica il file jml che contiene i risultati delle join
     *
     * @param Vector Contiene gli oggetti GeometryIntersection
     * @param Strin Id per creare i tag
     * @exception NullPointerException Se il Vector è null
     */
    public static void createJMLResult(Vector v, String nome, String id) throws NullPointerException{

        if( v == null )
            throw new NullPointerException("V is null");

        String localdir = System.getProperty("user.home");
        String path = localdir+"/Dropbox/stage/"+nome+".jml";
        boolean creazione = false;
        GeometryIntersection gi;
        Geometry g;
        char virgolette = '"';
        char virgola = ',';

        try{

            File file = new File(path);

            if( file.createNewFile() ){
                FileOutputStream fos = new FileOutputStream(file);
                PrintStream out = new PrintStream(fos);



                if (file.length() == 0) {
                    out.println("<?xml version='1.0' encoding='UTF-8'?>");
                    out.println("<JCSDataFile xmlns:gml=\"http://www.opengis.net/gml\" xmlns:xsi=\"http://www.w3.org/2000/10/XMLSchema-instance\">");
                    out.println("<JCSGMLInputTemplate>");
                    out.println("<CollectionElement>featureCollection</CollectionElement> ");
                    out.println("<FeatureElement>feature</FeatureElement>");
                    out.println("<GeometryElement>geometry</GeometryElement>");
                    out.println("<ColumnDefinitions>");
                    out.println("<column>");
                    out.println("<name>" + id + "</name>");
                    out.println("<type>INTEGER</type>");
                    out.println("<valueElement elementName=\"property\" attributeName=\"name\" attributeValue=\"" + id + "\"/>");
                    out.println("<valueLocation position=\"body\"/>");
                    out.println("</column>");
                    out.println("<column>");
                    out.println("<name>INTERS_ID</name>");
                    out.println("<type>INTEGER</type>");
                    out.println("<valueElement elementName=\"property\" attributeName=\"name\" attributeValue=\"INTERS_ID\"/>");
                    out.println("<valueLocation position=\"body\"/>");
                    out.println("</column>");
                    out.println("</ColumnDefinitions>");
                    out.println("</JCSGMLInputTemplate>");
                    out.println("");
                    out.println("<featureCollection>");
                    creazione = true;
                }

                for (int i = 0; i < v.size(); i++) {
                    scriviTagIniziali(out);

                    gi = (GeometryIntersection) v.elementAt(i);
                    g = gi.getGeometry();
                    Coordinate[] coord = g.getCoordinates();

                    for (int j = 0; j < coord.length; j++) {
                        out.print("" + coord[j].x);
                        out.print(virgola);
                        out.print("" + coord[j].y);
                        out.println("");
                    }


                    scriviTagFinaliResult(out, gi, id);
                }

                
                if (creazione) {
                    out.println("<!-- fine -->");
                    out.println("</featureCollection>");
                    out.println("</JCSDataFile>");
                }

                out.close();
                fos.close();

            }else{
                RandomAccessFile ra = new RandomAccessFile(file, "rw");
                long c = 0;
                String s = ra.readLine();
                while ( !s.equals("<!-- fine -->") )
                    s = ra.readLine();

                c = ra.getFilePointer();
                ra.seek(c - 14);

                for (int i = 0; i < v.size(); i++) {
                    gi = (GeometryIntersection) v.elementAt(i);
                    g = gi.getGeometry();
                    Coordinate[] coord = g.getCoordinates();

                    ra.writeBytes("<feature>\n");
                    ra.writeBytes("<geometry>\n");
                    ra.writeBytes("<gml:LineString>\n");
                    ra.writeBytes("<gml:coordinates>\n");
                    for (int j = 0; j < coord.length; j++) {

                        ra.writeBytes("" + coord[j].x);
                        ra.writeBytes(",");
                        ra.writeBytes("" + coord[j].y);
                        ra.writeBytes("\n");
                    }
                    ra.writeBytes("</gml:coordinates>\n");
                    ra.writeBytes("</gml:LineString>\n");
                    ra.writeBytes("</geometry>\n");
                    ra.writeBytes("<property name=\"" + id + "\">" + gi.getId() + "</property>\n");
                    ra.writeBytes("<property name=\"INTERS_ID\">" + gi.getIntersection_id() + "</property>\n");
                    ra.writeBytes("</feature>\n\n");

                }
                ra.writeBytes("<!-- fine -->\n");
                ra.writeBytes("</featureCollection>\n");
                ra.writeBytes("</JCSDataFile>\n");

                ra.close();
             }


        }catch(IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Metodo che scrive i tag finali
     *
     * @param PrintStream
     * @param GeometryIntersection
     * @param String
     */
    public static void scriviTagFinaliResult(PrintStream out, GeometryIntersection gi, String id) {
        out.println("</gml:coordinates>");
        out.println("</gml:LineString>");
        out.println("</geometry>");
        out.println("<property name=\""+id+"\">"+gi.getId()+"</property>");
        out.println("<property name=\"INTERS_ID\">"+gi.getIntersection_id()+"</property>");
        out.println("</feature>\n");
    }

}